/*
 * Copyright 2008-2013, David George, Licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.magneato.mvc;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.bind.JAXBException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.magneato.dto.Page;
import org.magneato.service.FileUploader;
import org.magneato.service.Repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.servlet.ModelAndView;

/**
 * This class is the FrontController for all operations concerning the
 * management of pages in the content repository. It is implemented using the
 * Spring MultiActionController. Different requests are mapped onto
 * corresponding methods with query string parameters mapped onto a WebPage
 * command object.
 * 
 * A set of Velocity templates are used to display the results.
 * 
 * @author David George
 */
@Controller
public class AdminController implements ServletContextAware {
	@Autowired
	private Repository repository;
	@Autowired
	private FileUploader fileUploader;

	@Autowired
	AclCache aclCache;

	private ServletContext servletContext;

	private Ehcache cache;

	private final Log _logger = LogFactory.getLog(AdminController.class);

	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;

	}

	@Value("${cacheName}")
	public void setCacheName(String cacheName) {
		CacheManager manager = CacheManager.getInstance();
		cache = manager.getEhcache(cacheName);
	}

	@RequestMapping(value = "/adminTool.do", method = RequestMethod.GET)
	public ModelAndView adminTool(HttpServletRequest request, String pageName) {
		ModelAndView mav = new ModelAndView("sys/admin");

		request.getSession().setAttribute("currentPage", pageName);
		mav.addObject("pageName", pageName);

		return mav;
	}

	@RequestMapping(value = "/userManager.do", method = RequestMethod.GET)
	public ModelAndView userManager(HttpServletRequest request) {
		ModelAndView mav = new ModelAndView("sys/userGrid");
		return mav;
	}

	/**
	 * Retrieves Page MetaData from the repository
	 * 
	 * @param request
	 * @param response
	 * @param page
	 * @return
	 */
	@RequestMapping(value = "/getPageDetails.do", method = RequestMethod.GET)
	public ModelAndView getPageDetails(HttpServletRequest request) {
		ModelAndView mav = new ModelAndView("sys/pageDetails");

		String pageName = (String) request.getSession().getAttribute(
				"currentPage");
		if (pageName == null) {
			mav = adminTool(request, pageName);
			mav.addObject("message", "Select a page to adminster");
			return mav;

		} else {
			Page page = repository.get(pageName);
			if (page != null) {
				// System.out.println(page.toString());
			}

			mav.addObject("page", page);
		}

		return mav;
	}

	/**
	 * Update Page
	 * 
	 * @param request
	 * @param response
	 * @param page
	 * @return
	 */
	@Secured({ "ROLE_ADMIN" })
	@RequestMapping(value = "/updatePageDetails.do", method = RequestMethod.POST)
	public ModelAndView updatePageDetails(HttpServletRequest request, Page page)
			throws Exception {
		// TODO: clear acl cache

		Page oldPage = repository.getForAdmin(page.getName());
		oldPage.setPerms(page.getPerms());
		oldPage.setCreateDate(page.getCreateDate());

		oldPage.setGroup(page.getGroup());
		oldPage.setAuthor(page.getAuthor());
		oldPage.setTitle(page.getTitle());
		oldPage.setEditTemplate(page.getEditTemplate());
		oldPage.setViewTemplate(page.getViewTemplate());
		oldPage.setContent(page.getContent());
		repository.addDocument(oldPage);
		ObjectIdentity objectIdentity = new ObjectIdentityImpl(String.class,
				page.getName());
		aclCache.evictFromCache(objectIdentity);

		ModelAndView mav = adminTool(request, page.getName());
		mav.addObject("page", oldPage);
		mav.addObject("message", "Page data updated successfully");
		return mav;
	}

	/**
	 * listPages
	 * 
	 * @param request
	 * @param response
	 * @param node
	 * @return
	 */
	@Secured({ "ROLE_ADMIN" })
	@RequestMapping(value = "/listPages.do", method = RequestMethod.GET)
	public ModelAndView listPages(String uuid) {

		ArrayList<Page> list = null;
		// list = webPageDAO.getPageList(uuid);
		return new ModelAndView("sys/pagelist", "list", list);
	}

	/**
	 * check name format - folders can't have extensions can't rename to self!!!
	 * 
	 * @param request
	 * @param response
	 * @param node
	 * @return
	 */
	@Secured({ "ROLE_ADMIN" })
	@RequestMapping(value = "/rename.do")
	public ModelAndView changeName(HttpServletRequest request, String newName) {
		ModelAndView mav = null;
		String message = null;

		String pageName = (String) request.getSession().getAttribute(
				"currentPage");

		if (pageName == null) {
			message = "Set a page to adminster";
		} else {
			if (newName == null) {
				mav = new ModelAndView("sys/rename");
				mav.addObject("name", Page.getShortName(pageName));
				return mav;
			}

			if (newName.isEmpty()) {
				message = "New page name is empty";
			} else if (newName.equals(Page.getShortName(pageName))) {
				message = "New page name is the same as the old name";
			} else {
				try {
					Page page = repository.rename(pageName, newName);
					pageName = page.getName();
					message = "Renamed " + pageName + " to " + newName;
				} catch (Exception e) {
					message = e.getLocalizedMessage();
				}

				/*
				 * // remove old page from cache String old = null; try { // old
				 * = webPageDAO.rename(id, name);
				 * 
				 * if (cache != null) { List cacheKeys =
				 * cache.getKeysWithExpiryCheck(); Iterator i =
				 * cacheKeys.iterator(); while (i.hasNext()) { String key =
				 * (String) i.next(); System.out.println("key<" + key + ">"); }
				 * 
				 * // we may want to evict the parent too! // and configure this
				 * // pattern // in a setter boolean removed =
				 * cache.remove("GET" + old + ".htmnull");
				 * 
				 * System.out.println("removed " + removed); cacheKeys =
				 * cache.getKeysWithExpiryCheck(); i = cacheKeys.iterator();
				 * while (i.hasNext()) { String key = (String) i.next();
				 * _logger.debug("key<" + key + ">"); } } } catch (Exception e)
				 * { message = e.toString(); }
				 */
			}
		}

		mav = adminTool(request, pageName);
		mav.addObject("message", message);
		return mav;

	}
	
	/**
	 * If a page name is set it will ask the user if they really want to delete the page.
	 * 
	 * @param request
	 * @return
	 */
	@RequestMapping(value = "/delete.do", method = RequestMethod.GET)
	public ModelAndView deletePage(HttpServletRequest request) {
		ModelAndView mav = null;
		String message = null;

		String pageName = (String) request.getSession().getAttribute(
				"currentPage");
		if (pageName == null) {
			message = "Set a page to adminster";
		} else {
			mav = new ModelAndView("sys/delete");
			mav.addObject("name", pageName);
			return mav;
		}
		
		mav = adminTool(request, pageName);
		mav.addObject("message", message);

		return mav;
	}
	
	@RequestMapping(value = "/delete.do",  method = RequestMethod.POST)
	public ModelAndView deletePage(HttpServletRequest request, String name) {
		
		String message = null;

		if (name != null) {
			message = delete(name);
			
			String url = request.getContextPath() + name;

			if (cache != null) {
				listCacheKeys();
				// we will want to evict all related pages plus home page
				System.out.println("removing " + url);

				cache.remove("GET" + url + ".htmnull");
				listCacheKeys();
			}
		} else {
			message = "Page name not specified for delete";
		}

		// TODO: we no longer have a current page..., maybe get parent?
		ModelAndView mav = adminTool(request, null);
		mav.addObject("message", message);

		return mav;
	}
	
	private void listCacheKeys() {
		List cacheKeys = cache.getKeysWithExpiryCheck();
		Iterator i = cacheKeys.iterator();
		while (i.hasNext()) {
			String key = (String) i.next();
			_logger.debug("key<" + key + ">");
			System.out.println("1.key<" + key + ">");
		}
	}
	
	// TODO remove from cache
	private String delete(String name) {
		String message;
		try {
			// get for delete
			Page page = repository.get(name);
			repository.delete(name);
			fileUploader.deleteAttachments(page);
			ObjectIdentity objectIdentity = new ObjectIdentityImpl(
					String.class, name);
			aclCache.evictFromCache(objectIdentity);
			message = "Deleted " + name;
		} catch (Exception e) {
			e.printStackTrace();
			message = e.getLocalizedMessage();
			_logger.error("Unable to delete page " + name + " because "
					+ message);
		}
		return message;
	}
	
	@Secured({ "ROLE_ADMIN" })
	@RequestMapping(value = "/deleteComment.do")
	public ModelAndView deleteComment(String name) {

		String stem = null;
		String message;
		try {
			stem = repository.delete(name);
			ObjectIdentity objectIdentity = new ObjectIdentityImpl(
					String.class, name);
			aclCache.evictFromCache(objectIdentity);
			message = "Deleted " + name;
		} catch (Exception e) {
			message = e.getLocalizedMessage();
			_logger.error("Unable to delete page " + name + " because "
					+ message);
		}
		
		return new ModelAndView("redirect:" + stem + ".htm");
	}


	@Secured({ "ROLE_ADMIN" })
	@RequestMapping(value = "/clearCache.do", method = RequestMethod.GET)
	public ModelAndView clearCache() {
		String message = "Clearing " + cache.getSize() + " from cache";
		cache.removeAll();

		return new ModelAndView("return", "message", message);

	}

	@Secured({ "ROLE_ADMIN" })
	@RequestMapping(value = "/updateTemplateForm.do", method = RequestMethod.GET)
	public ModelAndView updateTemplateForm(HttpServletRequest request,
			HttpServletResponse response) {
		// needs list of templates and scripts, scripts to be placed in???
		String scriptDir = servletContext.getRealPath("update-scripts");
		File dir = new File(scriptDir);
		System.out.println(dir.getAbsoluteFile());

		// It is also possible to filter the list of returned files.
		// This example does not return any files that start with `.'.
		FilenameFilter filter = new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return name.endsWith(".stx");
			}
		};
		String[] scripts = dir.list(filter);

		return new ModelAndView("sys/updateForm", "scripts", scripts);
	}

	// test update process, will output the results
	@Secured({ "ROLE_ADMIN" })
	@RequestMapping(value = "/updateTemplatexxx.do", method = RequestMethod.GET)
	public void updateTemplatexxx(HttpServletResponse response) {
		// FileCopyUtils.copy()
		try {
			ServletOutputStream sos = response.getOutputStream();
			sos.print("hello world");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * Runs an STX script to update a Magneato template (add or remove xml
	 * elements from the model and update the edit template). It expects to find
	 * the new template and script on a file defined on the server. These must
	 * be present before the method is called.
	 * 
	 * @param script
	 *            Name of script file to use, stored in XXX
	 * @param templateName
	 *            Name of template to update
	 * @param test
	 *            True will just produce the results of a merge
	 * @return
	 */
	@Secured({ "ROLE_ADMIN" })
	@RequestMapping(value = "/updateTemplate.do", method = RequestMethod.GET)
	public ModelAndView updateTemplate(String script, String templateName) {
		String scriptFile = servletContext.getRealPath("update-scripts/"
				+ script);
		File stxFile = new File(scriptFile);

		System.setProperty("javax.xml.transform.TransformerFactory",
				"net.sf.joost.trax.TransformerFactoryImpl");
		TransformerFactory factory = TransformerFactory.newInstance();

		// List<Page> nodes = webPageDAO.doQuery("//*[@"
		// + JCRWebPageDAO.PROP_TEMPLATE + " = '" + templateName + "']",
		// true);
		StringBuffer message = new StringBuffer();
		Transformer t = null;
		try {
			t = factory.newTransformer(new StreamSource(stxFile));
		} catch (TransformerException e) {
			System.err.println(e);
			// return here
		}
		// for (Page page : nodes) {
		// System.out.println("page " + page.getName());
		// System.out.println("contents " + page.getContents());
		//
		// message.append(page.getName());
		// message.append(" ");
		//
		// try {
		// InputStream is = new ByteArrayInputStream(page.getContents()
		// .getBytes("utf8"));
		//
		// ByteArrayOutputStream baos = new ByteArrayOutputStream();
		//
		// t.transform(new StreamSource(is), new StreamResult(baos));
		//
		// System.out.println(baos.toString());
		//
		// } catch (UnsupportedEncodingException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// } catch (TransformerException e) {
		// System.err.println(e);
		// }
		// }// for

		return new ModelAndView("return", "message", message);
	}

	// test update process, will output the results
	@RequestMapping(value = "/backup.do", method = RequestMethod.GET)
	public ModelAndView export(HttpServletRequest request) throws JAXBException {
		String pageName = (String) request.getSession().getAttribute(
				"currentPage");
		String message = "Cannot export current nameSpace " + pageName;
		if (pageName != null) {
			pageName = Page.getNameSpace(pageName);
			if (pageName != null) {
				repository.saveAsXML(pageName);
				message = "Exported: " + pageName;
			}
		}
		return new ModelAndView("sys/admin", "message", message);
	}

	// test update process, will output the results

	@RequestMapping(value = "/reindex.do", method = RequestMethod.GET)
	public ModelAndView reindex(HttpServletRequest request)
			throws JAXBException {
		String pageName = (String) request.getSession().getAttribute(
				"currentPage");
		String message = "Cannot reindex current nameSpace " + pageName;
		if (pageName != null) {
			pageName = Page.getNameSpace(pageName);
			if (pageName != null) {
				repository.reindex(pageName);
				message = "Reindexed: " + pageName;
			}
		}
		return new ModelAndView("sys/admin", "message", message);
	}

	@RequestMapping(value = "/changeNameSpace.do", method = RequestMethod.GET)
	public ModelAndView changeNameSpace(String oldNameSpace, String newNameSpace) {
		repository.changeNamespace(oldNameSpace, newNameSpace);

		return new ModelAndView("sys/admin", "message", "Exported");
	}

	@RequestMapping(value = "/restore.do", method = RequestMethod.GET)
	public ModelAndView restore(HttpServletRequest request)
			throws JAXBException {

		String pageName = (String) request.getSession().getAttribute(
				"currentPage");
		String message = "Cannot import into current nameSpace " + pageName;
		if (pageName != null) {
			pageName = Page.getNameSpace(pageName);
			if (pageName != null) {
				repository.loadFromXML(pageName);
				message = "Imported: " + pageName;
			}
		}
		return new ModelAndView("sys/admin", "message", message);
	}

	@RequestMapping(value = "/viewTemplate.do", method = RequestMethod.GET)
	public ModelAndView viewTemplate(String name) throws JAXBException {
		return new ModelAndView(name, "name", name);
	}

	@RequestMapping(value = "/postLogin.do", method = RequestMethod.GET)
	public ModelAndView postLogin(HttpServletRequest request) {
		HttpSession session = request.getSession();

		String redirect = (String) session.getAttribute("redirect");
		session.removeAttribute("redirect");
		if (redirect == null) {
			redirect = request.getContextPath();
		}
		System.out.println("redirect " + redirect + " context path "
				+ request.getContextPath());
		return new ModelAndView("redirect:" + redirect);
	}
}
